package jpave.dataholders;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.Statement;

import jpave.database.JPaveDBWrapper;
import jpave.query_interface.FieldMapper;
import jpave.query_interface.FieldTab;

import java.sql.Connection;

import assem.RStat;
import util.Debug.ErrorReport;
import util.data_structures.PrefixTree;
import util.methods.*;
import util.ui.CachedTable;

/**
 * Executes queries and stores the results for the "Basic Contig Query" feature
 * Part of the interface is in ui.ListQueryField
 */
public class BasicQueryData implements Serializable
{
	private static final long serialVersionUID = -1523901766612125734L;

	public BasicQueryData ( GlobalSettings settings )
    {
		theFilter = new FilterContigData( settings );
	}

	/**
	 * Execute the query and cache the results
	 * @param contigInclude Contigs to be displayed
	 * @param theAssemblies Assemblies to query
	 * @param mapper Fields to be pulled from the database
	 * @param strCacheFileName Name of the cache file to store results
	 * @return Number of results
	 */
	public int loadQueryResults ( String [] contigInclude, SessionData session, 
			FieldMapper mapper, FieldTab fTab, String strCacheFileName)
	{
		int count = 0;
	
		resetFieldIndexes ( );
		PrefixTree filterInclude = null;
		//Build the include list for contigs to display
		if(contigInclude != null) {
			filterInclude = new PrefixTree();
			for(int x=0; x<contigInclude.length; x++)
				filterInclude.put(contigInclude[x], contigInclude[x]);
		}
		
		String strAssemblyIDs = session.getAssemblyIDList( false );
		String strQuery = null;

		nContigIdx = mapper.getVisibleIndexForID(FilterContigData.CONTIG_ID_FIELD);
		strQuery = theFilter.getContigListSQL(mapper, fTab, strAssemblyIDs);
		try {
			count = runDBQuery(session, strQuery, filterInclude, mapper, fTab, strCacheFileName);
		} catch (Exception e) {
			String s = "Error: loading query results";
			System.err.println(s);
			ErrorReport.reportError(e, s);
		}
		return count;
	}

	private int runDBQuery ( SessionData session, String strQuery, 
			PrefixTree filterIncludeContigs, FieldMapper mapper, FieldTab fTab, 
			String strCacheFileName ) throws Exception
	{
		int count = 0;
		Connection conn = null;
		Statement stmt = null;
		
		if(StringHelpers.isEmpty(strQuery)) return 0;
		
		BufferedWriter p = null;
		if(!StringHelpers.isEmpty(strCacheFileName))
			p = new BufferedWriter(new FileWriter(strCacheFileName));

		if(p == null) {
			System.err.println("Error: your permissions do not allow writing a file");
			return 0;
		}
		
		RStat rStatCalcInc;
		int [] libCounts = JPaveDBWrapper.getLibCounts(conn, theFilter.getIncludeLibs());
		// Load the filter list of contigs
		try {
			if(theFilter.getExcludeLibs() != null)
				rStatCalcInc = new RStat(libCounts);
			else
				rStatCalcInc = null;

			Object[] row = null;
			conn = session.getConnection();
				stmt = conn.createStatement(
						ResultSet.TYPE_FORWARD_ONLY,
						ResultSet.CONCUR_READ_ONLY);
				// supposed to prevent memory hogging on large query
				stmt.setFetchSize(Integer.MIN_VALUE); 
				ResultSet rset = stmt.executeQuery(strQuery);
				
				while (rset.next()) {
					// Create the output row
					row = createSingleObjectArray(rset, filterIncludeContigs, mapper, 
							fTab, count+1, rStatCalcInc,  session);			

					// If passed filter, add to output list
					if (row != null) {
						count++; 
						if (p != null) {
							for (int i = 0;  i < row.length;  i++) {
								if (row[i] != null) {
									String s = row[i].toString();
									p.write(s, 0, s.length());
								}
								p.write(CachedTable.CACHE_FILE_DELIMITER, 0, 1);
							}
							p.newLine();
						}
					}
				}
		} finally {
			if (p != null) p.close();
			if (stmt != null) stmt.close();
			if (conn != null) conn.close();
		}
		
		return count; 
	}

	private void resetFieldIndexes ()
	{
		nContigIdx = -1;
	}

	/**
	 * Update the fields to be queried/stored
	 */
	public void setContigFilter ( FilterContigData inFilter ) {theFilter = inFilter;}
	
	/**
	 * Get the fields to be queried/stored
	 */
	public FilterContigData getContigFilter ( ) { return theFilter; }	

	private Object[] createSingleObjectArray ( 	ResultSet rset,
			PrefixTree filterIncludeContigs, 
			FieldMapper mapper, FieldTab fTab, 
			int nRecordID, RStat incLibs, SessionData session ) throws Exception
	{
		String strContig =  rset.getString( nContigIdx + 1 );

		// If we have a include filter, make sure both contig pass 
		// or include related is selected and at least one passed
		if ( filterIncludeContigs != null &&
				filterIncludeContigs.findExactObj( strContig ) == null )
			return null;
		Object[] array = mapper.getContigObjFromDB( rset, nRecordID,  theFilter, fTab, incLibs, session );

		return array;
	}

	/**
	 * Update the contigs to be stored
	 * @param contigs Array of contig IDs to include in the query results
	 */
	public void setContigs(String [] contigs) { this.contigs = contigs; }
	
	/**
	 * Get the included contigs for a query
	 */
	public String [] getContigs() { return contigs; }
	
	/**
	 * Clear memory associated with the object
	 */
	public void clear() {
		if(theFilter != null) theFilter.clear();
	}
	
	transient private int nContigIdx = -1;

	private String [] contigs = null;
	private FilterContigData theFilter = null;
}
